#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<ctype.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<fcntl.h>
#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{ p += (strlen(p)- 1); while(*p != '/') p--; }while(0)

#define None_Redir 0 //没有重定向
#define IN_Redir   1 //标准输入
#define OUT_Redir  2 //标准输出
#define APP_Redir  3 //标准追加
#define SkipSpace(cmd, pos) do{ while(1){ if(isspace(cmd[pos])) pos++;else break;}}while(0)

int redir_type = None_Redir;
char *filename = NULL;

char cwd[SIZE*4];

//定义一张全局的表
char *gArgv[NUM];

int lastcode = 0;


void Die()
{
  exit(1);
}

const char *GetHome()
{
  const char *home = getenv("HOME");
  if(home == NULL) return "/";
  return home;
}

const char *GetUserName()
{
  const char *name = getenv("LOGNAME");
  if(name == NULL) return "none";
  return name;
}

const char *GetHostName()
{
  static char buffer[256];
  char *hostname = buffer;
  if (gethostname(hostname, sizeof(buffer)) == 0) return hostname;
  return "none";
}

//获取当前的路径
const char *GetCwd()
{
  const char *cwd = getenv("PWD");
  if(cwd == NULL) return "none";

  return cwd;
}

void MakeCommendLineAndPrint()
{
  //实现输出一个命令行
  char line[SIZE];
  //获取三个字符串
  const char *username = GetUserName();
  const char *hostname = GetHostName();
  const char *cwd = GetCwd();
  SkipPath(cwd);
  //拼接字符串
  snprintf(line, sizeof(line), "%s@%s:%s^_^ -> ", username, hostname, strlen(cwd) == 1 ? "/" : cwd + 1);
  
  printf("%s", line);
  fflush(stdout);
}

//获取用户命令字符串
int GetUserCommend(char commend[], size_t n)
{
  //2.再定一个缓冲区
  //2.1.获取:从标准输入流中获取
  char *s = fgets(commend, n, stdin);
  if(s == NULL) return -1; 
  
  commend[strlen(commend) - 1] = ZERO;
  return strlen(commend);
}

void SplitCommend(char commend[], size_t n)
{
  //"ls -a -l -n" ---> "ls", "-a", "-l", "-n"
  gArgv[0] = strtok(commend, SEP);
  int index = 1;
  while(gArgv[index++] = strtok(NULL, SEP));//故意写成 = ，表示先赋值，再判断。刚好让gArgv最后一个元素是NULL，并且while判断结束
}


void ExecuteCommend()
{
 //n.执行命令：
  pid_t id = fork();

  //创建的子进程失败
  if(id < 0) Die();
  else if(id == 0)
  {
    //child
    //
    //重定向设置
    if(filename)
    {
      if(redir_type == IN_Redir)
      {
         int fd = open(filename, O_RDONLY);//因为是输入重定向
         dup2(fd, 0);//让标准输入直接指向我们的文件从而使命令从文件中读取
      }
      else if(redir_type == OUT_Redir)
      {
        int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, 0666);
        dup2(fd, 1);
      }
      else if(redir_type == APP_Redir)
      {
        int fd = open(filename,O_WRONLY | O_CREAT | O_APPEND, 0666);
        dup2(fd, 1);
      }
      else
      {

      }
    }
    execvp(gArgv[0],gArgv);
    exit(errno);
  }
  else
  {
    int status = 0; 
    pid_t rid = waitpid(id, &status, 0);

    if(rid == id)
    {
     // printf("father wait success!, child exit code : %d\n", WEXITSTATUS(status));
      lastcode = WEXITSTATUS(status);
      if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);  
    }
  }
}

 

//判断是哪一种cd
void Cd()
{
    const char* path = gArgv[1];
    
    if(path == NULL) path = GetHome();
    //PATH 一定存在
    chdir(path);
    //刷新环境变量
    char temp[SIZE*2];
    getcwd(temp, sizeof(temp));

    snprintf(cwd, sizeof(cwd),"PWD=%s", temp);//将cwd设置为一个全局变量，实时更新
    putenv(cwd); //再更新环境变量
}

void ReplaceEnvVars()
{
  for (int i = 0; gArgv[i] != NULL; i++)
  {
    if(gArgv[i][0] == '$')
    {
      if(gArgv[i][1] != '?')
      {
        char *var_name = gArgv[i] + 1; //跳过'$',获取变量名
        char *value = getenv(var_name);
        if(value)
        {
          //如果这个变量名已经在环境变量中存在
          gArgv[i] = strdup(value);
        }
        else
        {
          gArgv[i] = strdup("");
        }
      }
      
    }
   }
}


void Export()
{
  if(!gArgv[1])
  {
    fprintf(stderr, "export: missing argument!\n");
    return;
  }

  char *arg = strdup(gArgv[1]);

  char *eq = strchr(arg,'=');//查找是否有 = ，如果有就返回 = 的地址
  if(eq == NULL)
  {
    fprintf(stderr, "export: invalid format\n");
    free(arg);
    return;
  }

  *eq = '\0';//将 = 的位置的字符置为\0提前结束
  if(setenv(arg, eq+1, 1) != 0)
  {
    perror("export");
  }
  free(arg);
}


//检查是否是内建命令
int CheckBuildin()
{
  int yesorno = 0;
  const char *enter_cmd = gArgv[0];
  if(strcmp(enter_cmd, "cd") == 0)
  {
    yesorno = 1; 
    Cd();
  }
  else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1],"$?") == 0)
  {
    yesorno = 1;
    printf("%d\n", lastcode);
    lastcode = 0;
  }
  else if(strcmp(enter_cmd, "export") == 0)
  {
    Export();
    yesorno = 1;
  }
  return yesorno;
}



void CheckRedir(char cmd[])
{
  int pos = 0;
  int end = strlen(cmd);
  while(pos < end)
  {
    if(cmd[pos] == '>')
    {
      if(cmd[pos+1] == '>')//追加重定向
      {
        cmd[pos++] = 0;
        pos++;
        redir_type = APP_Redir;
        SkipSpace(cmd, pos);
        filename = cmd + pos;
      }
      else
      {
        cmd[pos++] = 0;
        redir_type = OUT_Redir;
        SkipSpace(cmd, pos);
        filename = cmd + pos;
      }
    }
    else if(cmd[pos] == '<')
    {
      cmd[pos++] = 0;//先赋值后++，将pos位置置为指向>符号后的第一个字符（有可能是空格
      redir_type = IN_Redir;
      SkipSpace(cmd, pos);//跳过所有的空格 ，由于文件名前可能会存在空格
      filename = cmd + pos;//保存文件的起始地址
    }
    else
    {
      pos++;
    }
  }
}

int main()
{
  int quit = 0;
  while(!quit)
  {
    //0.重置参数
    redir_type = None_Redir;
    filename = NULL;
    //1.自己输出一个命令行
    MakeCommendLineAndPrint();
    //2.获取用户命令字符串
    char usercommend[SIZE];
    int n = GetUserCommend(usercommend, sizeof(usercommend));
    if(n <= 0) return 1;
    //2.1 checkredir  ---检查是否有重定向
    CheckRedir(usercommend);
    //2.2 debug
    //printf("cmd: %s\n", usercommend);
    //printf("redir: %d\n", redir_type);
    //printf("filename: %s\n", filename);

    //3.命令行字符串分割
    SplitCommend(usercommend, sizeof(usercommend));

    //4.0 替换函数
    ReplaceEnvVars();
    //4.1检查是否是内建命令
    n = CheckBuildin();
    if(n) continue;

    //5.执行命令
    ExecuteCommend();

  }
   return 0; 
 }
